package main;

import java.util.*;
import org.eclipse.swt.graphics.ImageData;

import lightObjects.lightObject;
import main.Parser.ParseException;
import sceneObjects.*;
import utils.*;
import utils.Vector;

public class Scene implements Parseable {

    private ColorRGB bgColor;

    private String bgImgFileName;
    private ImageData bgImg;
    private ColorRGB ambientLight;

    private int canvasWidth = 0;
    private int canvasHeight = 0;

    private Camera pinCam;

    private int superSampleWidth = 1;

    private List<sceneObject> sceneObjects;
    private List<lightObject> lightObjects;

    public void setCanvasSize(int width, int height) {
	canvasWidth = width;
	canvasHeight = height;
    }

    public Scene() {
	bgColor = new ColorRGB(0, 0, 0);
	ambientLight = new ColorRGB(0, 0, 0);
	sceneObjects = new ArrayList<sceneObject>();
	lightObjects = new ArrayList<lightObject>();
    }

    public void add(Parseable obj) {
	if (obj.isCamera()) {
	    pinCam = (Camera) obj;
	    return;
	} else if (obj.isLight()) {
	    lightObjects.add((lightObject) obj);
	} else {
	    sceneObjects.add((sceneObject) obj);
	}
    }

    public boolean setParameter(String name, String[] args) throws ParseException {
	if (name.equals("background-col")) {
	    if (args.length != 3) {
		return false;
	    }
	    bgColor.deepCopy(Parser.stringArrToDoubleArr(args));
	    return true;
	} else if (name.equals("background-tex")) {
	    if (args.length != 1) {
		return false;
	    }
	    bgImgFileName = args[0];
	    return true;
	} else if (name.equals("super-samp-width")) {
	    if (args.length != 1) {
		return false;
	    }
	    superSampleWidth = Integer.parseInt(args[0]);
	    return true;
	} else if (name.equals("ambient-light")) {
	    if (args.length != 3) {
		return false;
	    }
	    ambientLight = new ColorRGB(Parser.stringArrToDoubleArr(args));
	    return true;
	}

	// We didn't find any parameter
	return false;
    }

    public boolean finalizeObject() throws Parser.ParseException {

	if (bgImgFileName != null) {
	    try {
		bgImg = new ImageData((RayTracer.getPathName() + "/" + bgImgFileName));
		bgImg = bgImg.scaledTo(canvasWidth, canvasHeight);
	    } catch (Exception e) {
		System.out.println(e.getMessage());
		return false;

	    }
	}

	if (pinCam == null) {
	    throw new Parser.ParseException("Fatal Error: scene does not contain a camera!");
	}

	if (superSampleWidth < 1) {
	    superSampleWidth = 1;
	}

	return true;
    }

    private ColorRGB getBackgroundColor(double x, double y) {
	if (bgImg != null) {
	    int col = bgImg.getPixel((int) x, (int) y);
	    return new ColorRGB(col);
	}
	return bgColor;
    }

    public ColorRGB getColor(double x, double y) {
	ColorRGB color = null;

	Point3D gridPoint = pinCam.getScreenPoint(x, y, canvasWidth, canvasHeight);
	Ray ray = pinCam.buildRayTowards(gridPoint);
	Intersection intersect = getMinimalIntersection(ray);
	if (intersect == null) {
	    return getBackgroundColor(x, y);
	}
	color = calculateColor(intersect, 1);
	return color;
    }

    private Intersection getMinimalIntersection(Ray ray) {
	Intersection intersect = null;
	Intersection tempIntersect = null;

	double distance = Double.POSITIVE_INFINITY;
	double tempDistance;

	for (sceneObject obj : sceneObjects) {
	    tempIntersect = obj.getIntersect(ray);
	    if (tempIntersect != null) {
		tempDistance = ray.getSource().distance(tempIntersect.getPoint());
		if (tempDistance < distance) {
		    distance = tempDistance;
		    intersect = tempIntersect;
		}
	    }
	}

	return intersect;
    }

    private ColorRGB calculateColor(Intersection intersect, int recursionDepth) {

	if (recursionDepth == -1) {
	    return new ColorRGB(0, 0, 0);
	}

	Ray lightRay;

	Intersection curIntersect;
	Intersection refIntersect;

	Point3D intersectionPoint = intersect.getPoint();
	sceneObject intersectedObject = intersect.getObject();
	Vector V = new Vector(intersectionPoint, pinCam.getEyePosition()).normalize();

	Vector N = intersectedObject.getNormalAtPoint(intersect);

	ColorRGB Kd = intersectedObject.getKD(intersect);
	ColorRGB Ks = intersectedObject.getKS();
	double Kr = intersectedObject.getKR();
	double np = intersectedObject.getNS();

	ColorRGB diffuseColor = new ColorRGB(0, 0, 0);
	ColorRGB specularColor = new ColorRGB(0, 0, 0);
	ColorRGB ambientColor = new ColorRGB(0, 0, 0);
	ColorRGB reflectionColor = new ColorRGB(0, 0, 0);

	for (lightObject lightSource : lightObjects) {
	    for (lightObject light : lightSource.getLightSources()) {
		lightRay = light.buildRayFrom(intersectionPoint);
		curIntersect = getMinimalIntersection(lightRay);

		if (curIntersect == null || (light.getDistanceFromPoint(lightRay.getSource()) < lightRay.getSource().distance(curIntersect.getPoint()))) {
		    Vector L = lightRay.getDirection();

		    double NLp = N.dotProduct(L);
		    ColorRGB Ip = light.getIllumination(intersectionPoint);

		    // Calculate the diffuse fraction
		    diffuseColor = diffuseColor.add(Kd.multiply(Math.abs(NLp)).multiply(Ip));

		    // Calculate the specular fraction
		    Vector R = N.scale(2 * NLp).substract(L).normalize();
		    specularColor = specularColor.add(Ks.multiply(Math.pow(Math.abs(V.dotProduct(R)), np)).multiply(Ip));
		}
	    }
	}

	// Calculate the reflection fraction
	if (Kr > 0) {
	    double NV = N.dotProduct(V);
	    Vector reflectionR = N.scale(2 * NV).substract(V).normalize();
	    Ray reflectionRay = new Ray(intersectionPoint, reflectionR.scale(-1).normalize());
	    refIntersect = getMinimalIntersection(reflectionRay);
	    if (refIntersect == null) {
		reflectionColor = bgColor.multiply(Kr);
	    } else {
		reflectionColor = calculateColor(refIntersect, recursionDepth - 1).multiply(Kr);
	    }
	}

	// Calculate the ambient fraction
	ambientColor = intersectedObject.getKA().multiply(ambientLight);

	ColorRGB ret = diffuseColor.add(specularColor).add(ambientColor).add(reflectionColor).trim();
	return ret;
    }

    public int getSuperSampleWidth() {
	return superSampleWidth;
    }

    public boolean isCamera() {
	return false;
    }

    public boolean isLight() {
	return false;
    }

}
